package io.github.yidasanqian.service;

import com.netflix.hystrix.*;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import io.github.yidasanqian.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * https://github.com/Netflix/Hystrix/wiki/Configuration
 */
@Service
public class UserService {

    private static final Logger log = LoggerFactory.getLogger(HelloService.class);

    private static final HystrixCommandKey USER_COMMAND_KEY = HystrixCommandKey.Factory.asKey("UserCommand");
    private static final HystrixThreadPoolKey USER_THREAD_POOL_KEY = HystrixThreadPoolKey.Factory.asKey("UserService");
    private static final String USER_GROUP_KEY = "UserServiceGroup";

    @Autowired
    private RestTemplate restTemplate;


    @CacheResult(cacheKeyMethod = "getUserByIdCacheKey")    // Hystrix注解缓存
    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand(fallbackMethod = "getUserByIdFallback")
    // 运行Hystrix命令
    public User getUserById(long id) {
        User user = restTemplate.getForObject("http://USER-SERVICE/user/{1}", User.class, id);
        if (user == null) {
            log.info("返回user:{}", user);
            user = new User(1L, "yidasanqian", 25);
        }
        return user;
    }

    private String getUserByIdCacheKey(long id) {
        log.info("获取用户缓存key");
        return String.valueOf(id);
    }

    private User getUserByIdFallback(long id, Throwable throwable) {
        log.info("获取用户服务降级异常：{}", throwable.getMessage());
        return new User(id, "error name", 22);
    }

    @CacheRemove(commandKey = "getUserById") // Hystrix注解清除缓存
    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand
    public void update(@CacheKey("id") User user) {
        restTemplate.put("http://USER-SERVICE/user", user, User.class);
    }

    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser(batchMethod = "findAll", collapserProperties = {
            @HystrixProperty(name = "timerDelayInMilliseconds", value = "100")    // 会覆盖hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的值
            , @HystrixProperty(name = "requestCache.enabled", value = "true")
    }) // 运行Hystrix请求合并, 100ms内的所有请求都合并到方法==》findAll
    public User find(String id) {
        log.info("find id:{}", id);
        return restTemplate.getForObject("http://USER-SERVICE/user/{1}", User.class, id);
    }

    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand
    public List<User> findAll(List<String> ids) {
        String uriVariable = String.join(",", ids);
        log.info("findAll uriValiables:{}", uriVariable);
        return restTemplate.getForObject("http://USER-SERVICE/user?ids={1}", List.class, uriVariable);
    }

    public String userCommandRequest() {
        long start = System.currentTimeMillis();
        StringBuilder result = new StringBuilder();

        User user = new User();
        user.setId(1L);
        user.setAge(22);
        user.setName("yidasanqian");

        // HystrixRequestContext.initializeContext();
        UserPostCommand userPostCommand = new UserPostCommand(restTemplate, user);
        user = userPostCommand.execute();
        log.info("execute result:{}", user);
        result.append(user);
        long end = System.currentTimeMillis();

        log.info("Spend time : " + (end - start) + "ms");
        return result.toString();
    }

    public String userCommandGetRequest() {
        long start = System.currentTimeMillis();
        StringBuilder result = new StringBuilder();

        // HystrixRequestContext.initializeContext();
        UserGetCommand userGetCommand = new UserGetCommand(restTemplate, 1L);
        User user = userGetCommand.execute();
        log.info("Get execute result:{}", user);
        result.append(user);
        long end = System.currentTimeMillis();

        log.info("Spend time : " + (end - start) + "ms");
        return result.toString();
    }

    static class UserGetCommand extends HystrixCommand<User> {

        private RestTemplate restTemplate;
        private Long id;

        protected UserGetCommand(RestTemplate restTemplate, Long id) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(USER_GROUP_KEY)).andCommandKey(USER_COMMAND_KEY).andThreadPoolKey(USER_THREAD_POOL_KEY));
            this.restTemplate = restTemplate;
            this.id = id;
        }

        @Override
        protected User run() throws Exception {
            return restTemplate.getForObject("http://USER-SERVICE/user/{1}", User.class, id);
        }

        /**
         * 清理缓存中失效的User<br>
         * 报异常：Request caching is not available. Maybe you need to initialize the HystrixRequestContext<br>
         * 原因： http://blog.csdn.net/lvyuan1234/article/details/76691112
         *
         * @param id user‘s id
         */
        public static void flushCache(Long id) {
            log.info("清理缓存中失效的User id:{}", id);
            HystrixRequestCache.getInstance(USER_COMMAND_KEY, HystrixConcurrencyStrategyDefault.getInstance())
                    .clear(String.valueOf(id));
        }

        /**
         * 开启请求缓存功能
         */
        @Override
        protected String getCacheKey() {
            log.info("开启请求缓存功能");
            return String.valueOf(this.id);
        }
    }

    class UserPostCommand extends HystrixCommand<User> {
        private RestTemplate restTemplate;
        private User user;

        protected UserPostCommand(RestTemplate restTemplate, User user) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(USER_GROUP_KEY)).andCommandKey(USER_COMMAND_KEY).andThreadPoolKey(USER_THREAD_POOL_KEY));
            this.restTemplate = restTemplate;
            this.user = user;
        }

        @Override
        protected User run() throws Exception {
            User u = restTemplate.postForObject("http://USER-SERVICE/user", user, User.class);
            UserGetCommand.flushCache(user.getId());
            return u;
        }
    }
}
